package com.xlh.support.doccheck.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.ZipUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.xlh.exception.common.GlobalException;
import com.xlh.properties.ResourcesProperties;
import com.xlh.support.doccheck.domain.SimilarFile;
import com.xlh.support.doccheck.service.DocCheckService;
import com.xlh.util.DocCheckUtil;
import com.xlh.util.SpringContextUtil;
import com.xlh.vo.CascaderVO;
import de.innosystec.unrar.Archive;
import de.innosystec.unrar.NativeStorage;
import de.innosystec.unrar.rarfile.FileHeader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.xlh.support.doccheck.constant.DocCheckConstant.DOC_CHECK_DIR;

/**
 * @author cheer
 */
@Service
@Slf4j
public class DocCheckServiceImpl implements DocCheckService, ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;


    private static ResourcesProperties config = SpringContextUtil.getBean(ResourcesProperties.class);

    /**
     * 词库是否加载完毕
     */
    private volatile boolean loaded = false;

    private Cache<String, List<SimilarFile>> similarFileCache = CacheBuilder.newBuilder()
            .expireAfterAccess(1, TimeUnit.DAYS)
            .build();

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 初始化加载
        taskExecutor.execute(() -> {
            log.info("加载词库开始");
            DocCheckUtil.similarityString("1", "1");
            try {
                Class.forName("org.apdplat.word.corpus.Bigram");
            } catch (ClassNotFoundException e) {
                log.error(e.getMessage(), e);
            }
            log.info("加载词库完毕");
            loaded = true;
        });
    }

    @Override
    public void uploadFile(Long userId, MultipartFile file) {
        if (!loaded) {
            throw new GlobalException("词库加载中，请稍等一下再使用查重工具");
        }

        boolean isZip = StringUtils.endsWith(file.getOriginalFilename(), "zip");
        boolean isRar = StringUtils.endsWith(file.getOriginalFilename(), "rar");
        if (!isZip && !isRar) {
            throw new GlobalException("文件必须为zip或rar压缩文件");
        }

        String filePath = DOC_CHECK_DIR + userId;
        FileUtil.clean(filePath);
        FileUtil.mkdir(filePath);

        String fileFullName = filePath + File.separator + file.getOriginalFilename();
        try {
            file.transferTo(new File(fileFullName));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new GlobalException("上传文件异常");
        }

        if (isZip) {
            ZipUtil.unzip(fileFullName, filePath, Charset.forName("gbk"));
        }
        if (isRar) {
            unrar(fileFullName, filePath);
        }
        FileUtil.del(fileFullName);

        similarFileCache.invalidate(String.valueOf(userId));
        List<SimilarFile> similarFiles = compareFileSimilarity(filePath);
        similarFileCache.put(String.valueOf(userId), similarFiles);
    }

    public void uploadFileOther(String userId, MultipartFile file, Long courseId, Long chapterId, Long id) {
        if (!loaded) {
            throw new GlobalException("词库加载中，请稍等一下再使用查重工具");
        }

        boolean isZip = StringUtils.endsWith(file.getOriginalFilename(), "zip");
        boolean isRar = StringUtils.endsWith(file.getOriginalFilename(), "rar");
        if (!isZip ) {
            throw new GlobalException("文件必须为zip压缩文件");
        }

        String filePath = getBasePath(courseId, chapterId, id, userId) ;
        FileUtil.clean(filePath);
        FileUtil.mkdir(filePath);

        String fileFullName = filePath + File.separator + file.getOriginalFilename();
        try {
            file.transferTo(new File(fileFullName));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new GlobalException("上传文件异常");
        }
        if (isZip) {
            try {
                ZipUtil.unzip(fileFullName, filePath, Charset.forName("gbk"));
            } catch (RuntimeException e) {
                log.error(e.getMessage(), e);
                throw new GlobalException("上传文件异常");
            }
        }

    }

    /**
     * 获取随堂练习附件题上传路径
     * @param courseId
     * @param chapterId
     * @param id
     * @param userId
     * @return
     */
    public String getBasePath(Long courseId, Long chapterId, Long id, String userId ){

        String path = config.getPractice().getLocalDir();
        String basePath =  path + File.separator+"accessory"+
                                  File.separator+courseId+File.separator+chapterId+
                                  File.separator+id+File.separator+userId;

        return basePath;
    }


    @Override
    public List<CascaderVO> viewFile(Long userId) {
        String filePath = DOC_CHECK_DIR + userId;
        FileUtil.mkdir(filePath);
        return listFiles(filePath);
    }

    public List<CascaderVO> viewFileOther(String userNumber, Long courseId, Long chapterId,  Long id) {

        String filePath = getBasePath( courseId, chapterId, id, userNumber );
        FileUtil.mkdir(filePath);

        return listFiles(filePath);
    }
    public String downFile(String userNumber,Long courseId,Long chapterId,String fileName,Long id) {

        String filePath = getBasePath( courseId, chapterId, id, userNumber )+ File.separator + fileName;

        return filePath;
    }
    @Override
    public List<SimilarFile> querySimilarFile(Long userId, Double similarity) {
        List<SimilarFile> similarFiles = similarFileCache.getIfPresent(String.valueOf(userId));
        if (CollectionUtils.isEmpty(similarFiles)) {
            return new ArrayList<>();
        }

        return similarFiles.stream()
                .filter(similarFile -> similarFile.getSimilarityScore() >= similarity)
                .collect(Collectors.toList());
    }

    @Override
    public void deleteFile(Long userId) {
        String filePath = DOC_CHECK_DIR + userId;
        FileUtil.clean(filePath);
        similarFileCache.invalidate(String.valueOf(userId));
    }

    private void unrar(String fileFullName, String filePath) {
        String os = System.getProperty("os.name");
        if (os != null && os.toLowerCase().startsWith("win")) {
            createDirectory(fileFullName, filePath);
            unrarFile(fileFullName, filePath);
        } else {
            String cmd = "unrar X -o+ " + fileFullName + " " + filePath;
            try {
                Process proc = Runtime.getRuntime().exec(cmd);
                proc.waitFor();
            } catch (Exception e) {
                log.error("unrar error", e);
            }
        }
    }

    /**
     * 创建解压缩需要的文件夹
     */
    private void createDirectory(String fileFullName, String filePath) {
        try (Archive archive = new Archive(new NativeStorage(new File(fileFullName)))) {
            FileHeader fileHeader = archive.nextFileHeader();
            while (fileHeader != null) {
                if (fileHeader.isDirectory()) {
                    String fileName;
                    if (fileHeader.isUnicode()) {
                        fileName = fileHeader.getFileNameW().trim();
                    } else {
                        fileName = fileHeader.getFileNameString().trim();
                    }
                    FileUtil.mkdir(filePath + File.separator + fileName);
                }
                fileHeader = archive.nextFileHeader();
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 解压缩文件
     */
    private void unrarFile(String fileFullName, String filePath) {
        try (Archive archive = new Archive(new NativeStorage(new File(fileFullName)))) {
            FileHeader fileHeader = archive.nextFileHeader();
            while (fileHeader != null) {
                if (!fileHeader.isDirectory()) {
                    String fileName;
                    if (fileHeader.isUnicode()) {
                        fileName = fileHeader.getFileNameW().trim().replace("\\", "/");
                    } else {
                        fileName = fileHeader.getFileNameString().replace("\\", "/");
                    }
                    File out = new File(filePath + File.separator + fileName);
                    try (FileOutputStream os = new FileOutputStream(out)) {
                        archive.extractFile(fileHeader, os);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
                fileHeader = archive.nextFileHeader();
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    private List<SimilarFile> compareFileSimilarity(String filePath) {
        List<File> files = new ArrayList<>();
        List<File> allFiles = FileUtil.loopFiles(filePath);
        if (CollectionUtils.isNotEmpty(allFiles)) {
            for (File item : allFiles) {
                boolean isWordFile = StringUtils.endsWith(item.getName(), "doc") ||
                        StringUtils.endsWith(item.getName(), "docx");
                if (item.isFile() && isWordFile) {
                    files.add(item);
                }
            }
        }
        List<SimilarFile> similarFiles = new ArrayList<>();
        String docRootDir = filePath + File.separator;
        if (CollectionUtils.isNotEmpty(files)) {
            for (int i = 0; i < files.size() - 1; i++) {
                for (int j = i + 1; j < files.size(); j++) {
                    String filePathA = files.get(i).getAbsolutePath();
                    String filePathB = files.get(j).getAbsolutePath();
                    SimilarFile similarFile = SimilarFile.builder()
                            .file(StringUtils.substringAfter(filePathA, docRootDir))
                            .comparedFile(StringUtils.substringAfter(filePathB, docRootDir))
                            .similarityScore(DocCheckUtil.similarity(filePathA, filePathB))
                            .build();
                    similarFiles.add(similarFile);
                }
            }
        }
        return similarFiles;
    }

    private List<CascaderVO> listFiles(String filePath) {
        List<CascaderVO> cascaderVOs = new ArrayList<>();
        File root = new File(filePath);
        File[] allFiles = root.listFiles();
        if (allFiles != null) {
            for (File file : allFiles) {
                if (file.getName().indexOf(".zip") == -1){

                    CascaderVO cascaderVO = CascaderVO.builder()
                            .label(file.getName())
                            .children(file.isDirectory() ? listFiles(file.getAbsolutePath()) : null)
                            .build();
                    cascaderVOs.add(cascaderVO);
                }
            }
        }
        return cascaderVOs;
    }
}
